#region File Description 
///////////////////////////////////////////////////////////////////////////////
/// \file GameOfLife.cs
/// \brief This file stores the GameOfLife (Game) Class
/// \note It loads and manages the Game of Life Applications and Resources
/// \author Steven Duda
/// \date 18/7/2010
/// \remarks Copyright(c) 2010 LM01 Project Group (Animus Games - ViralFlux)
/// \remarks Some Rights Reserved.
/// Licenced under: Microsoft Reciprocal License (Ms-RL)
/// For More Information see: www.microsoft.com/opensource/licenses.mspx
///////////////////////////////////////////////////////////////////////////////
#endregion

#region Using Statements 
    using System;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Graphics;
	using Microsoft.Xna.Framework.Audio;
	using System.IO;
    using System.Collections.Generic;
#endregion

namespace GameOfLife
{
    /// <summary>
    /// \class GameOfLifeClass
    /// \author Steven Duda, Brad Mclain
    /// \brief This is the main type of the Game of Life Program.
    /// </summary>
    internal sealed class GameOfLifeClass : Microsoft.Xna.Framework.Game
    {
        #region Class-Level Variables 
        
        private const double UPDATE_INTERVAL = 1000.0;
        static private bool ecosystemUpdate;
        static private bool QueuedUpdate;
        static private uint iterations = 0;

        // GOL Sprite Cell Grid
        static private uint gridHeight;
        static private uint gridWidth;
        static private uint gridSize;
        static private GOLSprite[] cellGrid;

        //Managers
        static private ContentManager content;
        static private GraphicsDeviceManager graphics;
        static private SpriteBatch spriteBatch;

        //Textures
        static private Texture2D onTexture;
        static private Texture2D offTexture;
        static private Texture2D resetButtonTexture;
        static private Texture2D stopButtonTexture;
        static private Texture2D startButtonTexture;
        static private Texture2D stepButtonTexture;
        static private Texture2D seedButtonTexture;
        static private Texture2D exitButtonTexture;

        //Buttons
        static private Button resetButton;
        static private Button stopButton;
        static private Button startButton;
        static private Button stepButton;
        static private Button seedButton;
        static private Button exitButton;

		//Sound
		private static SoundEffect click;

        //Frames per Second (FPS) Measuring
        static private int _totalFrames = 0;
        static private int _fps = 0;
        static private double _elapsedTime = 0.0;
        static private SpriteFont _fpsFont;
        static private Vector2 _fpsPos;
        static private Vector2 _iterPos;

        #endregion

		#region Private Methods

		private static void FlushConsoleBuffer()
        {
            while (Console.In.Peek() != -1)
                Console.In.Read();
        }

        private static void SetGraphicsMaximised(object sender, PreparingDeviceSettingsEventArgs e)
        {
            DisplayMode displayMode = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode;
            e.GraphicsDeviceInformation.PresentationParameters.BackBufferFormat = displayMode.Format;
            e.GraphicsDeviceInformation.PresentationParameters.BackBufferWidth = displayMode.Width;
            e.GraphicsDeviceInformation.PresentationParameters.BackBufferHeight = displayMode.Height-40;
        }

        private static bool SetResolution(GraphicsDeviceManager graphicsDev)
        {
            bool isNotMaximised = true;
            Console.Clear();
            Console.Write("Please Select the Screen Resolution for Game of Life. \n\n");

            Console.Write(" Press 1 for 854x480 \n Press 2 for 1280x720 \n");
            Console.Write(" Press 3 for 1366x768 \n Press 4 for 1920x1080 \n");
            Console.Write(" Press 5 for 640x480 \n Press 6 for 800x600 \n");
            Console.Write(" Press 7 for 1024x768 \n Press 8 for 1400x1050 \n");
            Console.Write(" Press f for FULL-SCREEN \n");
            Console.Write(" Press m for Maximised [BETA] \n");
            Console.Write(" Default resolution: 1280x720 \n");
            Console.Write("Enter Selection: ");

            #region Resolution Selection
            switch (Console.Read())
            {
                case '1':
                    graphicsDev.PreferredBackBufferWidth = 854; graphicsDev.PreferredBackBufferHeight = 480;
                    break;
                case '2':
                    graphicsDev.PreferredBackBufferWidth = 1280; graphicsDev.PreferredBackBufferHeight = 720;
                    break;
                case '3':
                    graphicsDev.PreferredBackBufferWidth = 1366; graphicsDev.PreferredBackBufferHeight = 768;
                    break;
                case '4':
                    graphicsDev.PreferredBackBufferWidth = 1920; graphicsDev.PreferredBackBufferHeight = 1080;
                    break;
                case '5':
                    graphicsDev.PreferredBackBufferWidth = 640; graphicsDev.PreferredBackBufferHeight = 480;
                    break;
                case '6':
                    graphicsDev.PreferredBackBufferWidth = 800; graphicsDev.PreferredBackBufferHeight = 600;
                    break;
                case '7':
                    graphicsDev.PreferredBackBufferWidth = 1024; graphicsDev.PreferredBackBufferHeight = 768;
                    break;
                case '8':
                    graphicsDev.PreferredBackBufferWidth = 1400; graphicsDev.PreferredBackBufferHeight = 1050;
                    break;
                case '9':
                    graphicsDev.PreferredBackBufferWidth = 1600; graphicsDev.PreferredBackBufferHeight = 1200;
                    break;
                case 'f':
                    graphicsDev.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
                    graphicsDev.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
                    graphicsDev.IsFullScreen = true;
                    break;
                case 'm':
                    graphicsDev.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(SetGraphicsMaximised);
                    graphicsDev.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
                    graphicsDev.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height - 60;
                    isNotMaximised = false;
                    break;
                default:
                    graphicsDev.PreferredBackBufferWidth = 1280; graphicsDev.PreferredBackBufferHeight = 720;
                    break;
            }
            #endregion

            FlushConsoleBuffer();
            gridHeight = (uint)((graphicsDev.PreferredBackBufferHeight - 11) / 22);
            gridWidth = (uint)((graphicsDev.PreferredBackBufferWidth - 190) / 22);
            gridSize = gridHeight * gridWidth;

            //Apply Resolution
            if (graphicsDev.IsFullScreen == false)
            {
                graphicsDev.ApplyChanges(); // This is only required when device is already set
            }

            return (isNotMaximised);
		}

		#endregion

		#region Constructor

		internal GameOfLifeClass()
        {
            // Initialise the Graphics Device
            graphics = new GraphicsDeviceManager(this);

#if DEBUG
            //Enable Window Resizing
            //http://msdn.microsoft.com/en-us/library/bb203876.aspx
            this.Window.AllowUserResizing = SetResolution(graphics);
#else
            #region PUBLISH_FULLSCREEN

            int resWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
            int resHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
            graphics.PreferredBackBufferWidth = resWidth;
            graphics.PreferredBackBufferHeight = resHeight;
            graphics.IsFullScreen = true;

            gridHeight = (uint)((resHeight - 11) / 22);
            gridWidth = (uint)((resWidth - 170) / 22);
            gridSize = gridHeight * gridWidth;

            #endregion
#endif

            //Turn the Mouse On, and Reference the Content Directory
            this.IsMouseVisible = true;
            this.Content.RootDirectory = "Content";

            #region Frame Rate Tweaks

            //Control the Update Speed (note: Drawing fps is still limited to 60fps)
            //this.IsFixedTimeStep = false;
            //graphics.SynchronizeWithVerticalRetrace = IsFixedTimeStep;

            #endregion

		}

		#endregion

		#region Public Methods

		/// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // Create a new SpriteBatch, for the GOLSprites and Button Drawing
            spriteBatch = new SpriteBatch(GraphicsDevice);

            base.Initialize(); // Note: This calls LoadContent() internally

            // Create the Cell Grid
            #if DEBUG
                Console.WriteLine("Generating Grid...");
            #endif

            const uint textureSize = 22;  // assumes square texture + 10% buffer
            const uint edgeSpacing = 10;  // Spacing from Top and Left window edges
            uint xBound = edgeSpacing + ((gridWidth - 1) * textureSize);
            uint xPos = edgeSpacing;
            uint yPos = edgeSpacing;
            cellGrid = new GOLSprite[gridSize];

            // Set the Shared Texture for GOLSprites
            GOLSprite.onTexture = onTexture;
            GOLSprite.offTexture = offTexture;

            for (uint i = 0; i < gridSize; ++i)
            {
                cellGrid[i] = new GOLSprite(spriteBatch, new Vector2(xPos, yPos), false);

                if (xPos < xBound)
                {
                    xPos += textureSize;
                }
                else
                {
                    yPos += textureSize;
                    xPos = edgeSpacing;
                }
            }
            //End Cell Grid Create

            // Add Game Buttons
            xBound += 40; // new xBound for Buttons

            resetButton = new Button(resetButtonTexture, new Vector2(xBound, 30));
            seedButton = new Button(seedButtonTexture, new Vector2(xBound, 80));
            startButton = new Button(startButtonTexture, new Vector2(xBound, 130));
            stepButton = new Button(stepButtonTexture, new Vector2(xBound, 180));
            stopButton = new Button(stopButtonTexture, new Vector2(xBound, 230));
            exitButton = new Button(exitButtonTexture, new Vector2(xBound, yPos - 40));

            //Frames per Second (FPS) Text Position
            _fpsPos = new Vector2(xBound-6, yPos - 110);
            _iterPos = new Vector2(xBound-6, yPos - 80);

            // Apply a Generic Life-life Ruleset to the GOLEcosystem
            ////////////////////////////////////////////////////////////

			// Life!
			List<int> BirthList = new List<int> { 3 };
			List<int> SurvivalList = new List<int> { 2, 3 };

			if (File.Exists("GameOfLife.ini"))
			{
				//Read the First 2 Lines for GameOfLife.ini
				TextReader trStream = new StreamReader("GameOfLife.ini");
				trStream.ReadLine(); //ignore first line
				string[] Births = (trStream.ReadLine()).Split(',');
				string[] Survivors = (trStream.ReadLine()).Split(',');
				trStream.Close();

				BirthList.Clear();
				foreach (string birthVal in Births)
				{
					if (!String.IsNullOrEmpty(birthVal))
					BirthList.Add(Convert.ToInt32(birthVal.Trim()));
				}

				SurvivalList.Clear();
				foreach (string surviveVal in Survivors)
				{
					if (!String.IsNullOrEmpty(surviveVal))
					SurvivalList.Add(Convert.ToInt32(surviveVal.Trim()));
				}
			}


            GOLEcosystem.AddGenericRuleset(BirthList, SurvivalList);
            ////////////////////////////////////////////////////////////

            //Load Ecosystem
            GOLEcosystem.LoadFromDimensions(gridWidth, gridHeight);

            ecosystemUpdate = false;
            QueuedUpdate = false;
#if DEBUG
            Console.Clear();
#endif

		}

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            if (content == null)
                content = new ContentManager(this.Services, "Content");

            onTexture = content.Load<Texture2D>(@"Sprites/GridOn");
            offTexture = content.Load<Texture2D>(@"Sprites/GridOff");

            resetButtonTexture = content.Load<Texture2D>(@"Sprites/RESET");
            startButtonTexture = content.Load<Texture2D>(@"Sprites/START");
            stopButtonTexture = content.Load<Texture2D>(@"Sprites/STOP");
            stepButtonTexture = content.Load<Texture2D>(@"Sprites/STEP");
            seedButtonTexture = content.Load<Texture2D>(@"Sprites/SEED");
            exitButtonTexture = content.Load<Texture2D>(@"Sprites/EXIT");
            
            //Load Font
            _fpsFont = content.Load<SpriteFont>("FreeSans");

			//Load Sound
			click = Content.Load<SoundEffect>("click");

            base.LoadContent();
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            //! Updates the Ecosystem  <--
            _elapsedTime += gameTime.ElapsedGameTime.Milliseconds;
            if ((ecosystemUpdate && _elapsedTime >= UPDATE_INTERVAL) || QueuedUpdate)
            {
                //! -- Performance Test Loop -- !//
                // Specify How many Ticks per an Update (Performance Testing)
                //  i < 1 = 1 tick per Update
                //  i < RandNum.Next(1,2) = avg. 1.5 ticks per Update
                //  i < 2 = 2 ticks per Update
                //  i < RandNum.Next(2,3) = avg. 2.5 ticks per Update
                //  i < 3 = 3 ticks per Update
                //  i < RandNum.Next(3,4) = avg. 3.5 ticks per Update
                //  i < etc.
                // Random RandNum = new Random();     //<-- Uncomment to use RandNum 
                
                //for (int i = 0; i < 2; ++i)
                //{
                    iterations++;
                    GOLEcosystem.Tick(); //make the world tick every turn
                //}

                #if DEBUG
                    Console.Write("tick \n");
                #endif
            }
            bool hf = this.IsActive; //GOL has focus

            if (hf)
            {
                HandleInput();
            }

            // Update the Game Of Life Grid(Ecosystem)
            for (uint i = 0; i < gridSize; ++i)
            {
                if (hf)
                {
                    cellGrid[i].Update();
                    if (!cellGrid[i].Enabled)
                        continue;

                    if (cellGrid[i].isMiddleReleased() || cellGrid[i].isRightHeld() && (cellGrid[i].isPressed() || cellGrid[i].isHeld()))
                    {
                        #if DEBUG
                            Console.WriteLine("Node: {0}", i + 1);
                        #endif
                        //Toggle the GOL Cell Existance
                        GOLEcosystem.SetNodeState((i + 1), GOLNodeState.Dead);
                        GOLEcosystem.ToggleGOLCellExistance(i+1);
                        cellGrid[i].Enabled = !cellGrid[i].Enabled;
                        continue;
                    }
                }

                if (cellGrid[i].Enabled && (GOLEcosystem.GetNodeState((i + 1)) == GOLNodeState.Alive)
                     && (ecosystemUpdate || QueuedUpdate))
                {
                    cellGrid[i].IsOn = true;
                }
                else if (hf && !cellGrid[i].IsOn && cellGrid[i].isHeld())
                {
                    cellGrid[i].IsOn = true;
                    GOLEcosystem.SetNodeState((i + 1), GOLNodeState.Alive);
                    #if DEBUG
                        Console.WriteLine("Turned On: {0}", i+1);
                    #endif
                }

                if (cellGrid[i].Enabled && (GOLEcosystem.GetNodeState((i + 1)) == GOLNodeState.Dead)
                     && (ecosystemUpdate || QueuedUpdate))
                {
                    cellGrid[i].IsOn = false;
                }
                else if (hf && cellGrid[i].IsOn && cellGrid[i].isRightHeld())
                {
                    cellGrid[i].IsOn = false;
                    GOLEcosystem.SetNodeState((i + 1), GOLNodeState.Dead);
                    #if DEBUG
                        Console.WriteLine("Turned Off: {0}", i+1);
                    #endif
                }
            }

            QueuedUpdate = (hf && (stepButton.isPressed() || stepButton.isRightHeld()));

            //Measure Frames per Second (FPS), and refresh ElapsedTime
            //Note: THIS NEEDS TO MATCH THE GAME UPDATE TIME!!!
            if (_elapsedTime >= UPDATE_INTERVAL)
            {
                _fps = (int)((double)((_totalFrames-1) / (_elapsedTime / UPDATE_INTERVAL)) + 0.5) - 2;
				if (_fps < 0) { _fps = 0; }
                _totalFrames = 0;
                _elapsedTime = 0;
            }

            base.Update(gameTime);
        }

        private static void ResetGrid(bool reviveDeadCells, GOLNodeState resetState )
        {
            for (uint i = 0; i < gridSize; ++i)
            {
                //Bring Disabled Cells Back to Life
                if (!cellGrid[i].Enabled && reviveDeadCells)
                    GOLEcosystem.ToggleGOLCellExistance(i+1);

                GOLEcosystem.SetNodeState((i + 1), resetState);

                // Fix Interface Accordingly
                cellGrid[i].Enabled = true;
                cellGrid[i].IsOn = (resetState == GOLNodeState.Alive);
            }
            iterations = 0;
        }


        private void HandleInput()
        {
            //! Update Input State
            MouseStateManager.Update();

            //! Update Buttons (Check for Input)
            resetButton.Update();
            startButton.Update();
            stopButton.Update();
            stepButton.Update();
            seedButton.Update();
            exitButton.Update();
                
            if (resetButton.isReleased())               // Reset all cells to Active + Dead
            {
				click.Play();
                ResetGrid(true, GOLNodeState.Dead);
            }
            else if (resetButton.isMiddleReleased())    // Reset all cells to Active + Alive
            {
				click.Play();
				ResetGrid(true, GOLNodeState.Alive);
            }
            else if (resetButton.isRightReleased())     // Reload Entire Ecosystem to Initial
            {
				click.Play();
				GOLEcosystem.LoadFromDimensions(gridWidth, gridHeight);
                ResetGrid(false, GOLNodeState.Dead);
            }
            else if (exitButton.isReleased())           // Quit 'Game of Life'
            {
                Exit();
            }
            else if (stopButton.isReleased())           // Stop Automatic Ecosystem Update
            {
				click.Play();
				ecosystemUpdate = false;
            }
            else if (startButton.isReleased())          // Start Automatic Ecosystem Update
            {
				click.Play();
				ecosystemUpdate = true;
            }
            else if (seedButton.isReleased())           // Seed the Ecosystem
            {
				click.Play();
				//Seeds the Ecosystem
                Random Seed = new Random();
                int SeedVal = Seed.Next(10000);       // 10000 is an arbitary seed limit
                Random RandNum = new Random(SeedVal); // Can specify SeedVal for control if desired
                for (uint i = 0; i < gridSize; ++i)
                {
                    if (cellGrid[i].Enabled && RandNum.Next(0, RandNum.Next(1, 6)) == RandNum.Next(1, 5))
                    {
                        cellGrid[i].IsOn = true;
                        GOLEcosystem.SetNodeState((i + 1), GOLNodeState.Alive);
                    }
                }
#if DEBUG
                Console.WriteLine("Seed: " + SeedVal);
#endif
			}
		}

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.Bisque);
            //Viewport viewport = this.GraphicsDevice.Viewport;
            //Rectangle fullscreen = new Rectangle(0, 0, viewport.Width, viewport.Height);

            spriteBatch.Begin();

            //! Draws the Ecosystem  <--//testing code to see if stuff is loading
            for (uint i = 0; i < gridSize; ++i)
            {
                cellGrid[i].Draw();
            }

            //! Draw the Buttons!
            resetButton.Draw(spriteBatch);
            startButton.Draw(spriteBatch);
            stopButton.Draw(spriteBatch);
            stepButton.Draw(spriteBatch);
            seedButton.Draw(spriteBatch);
            exitButton.Draw(spriteBatch);
            
            //Update & Draw Frames per Second (FPS) Counter
            _totalFrames++;
            spriteBatch.DrawString(_fpsFont, string.Format("FPS: {0}", _fps),
                            _fpsPos, Color.Black);

            //Draw Iteration Count
            spriteBatch.DrawString(_fpsFont, string.Format("Iterations: {0}", iterations),
                            _iterPos, Color.Black);

            spriteBatch.End();

            base.Draw(gameTime);
		}
		#endregion
	}
}
